﻿#region Copyright
//       Copyright (c) 2009 - Antoine Jaussoin - http://www.jaussoin.com/blog/
// 
//        This program is free software: you can redistribute it and/or modify
//        it under the terms of the GNU General Public License as published by
//        the Free Software Foundation, either version 3 of the License, or
//        (at your option) any later version.
// 
//        This program is distributed in the hope that it will be useful,
//        but WITHOUT ANY WARRANTY; without even the implied warranty of
//        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//        GNU General Public License for more details.
// 
//        You should have received a copy of the GNU General Public License
//        along with this program.  If not, see <http://www.gnu.org/licenses/>.
#endregion
using System.Drawing;
using System.Globalization;

namespace FT.Architecture.Utils.ExtensionMethods
{
    /// <summary>
    /// Extensions methods for the Color class
    /// </summary>
    public static class ColourExtensions
    {
        /// <summary>
        /// Converts a Colour object to its HTML representation in hexadecimal.
        /// The returned string contains the # symbol.
        /// </summary>
        /// <param name="colour"></param>
        /// <returns></returns>
        public static string ToHtml(this Color colour)
        {
            return "#" + (colour.ToArgb() & 0x00FFFFFF).ToString("X6");
        }

        /// <summary>
        /// Converts an HTML hex colour to an Colour object
        /// </summary>
        /// <param name="colourInHtml">Colour as an hex string : #AAAAAA</param>
        /// <returns></returns>
        public static Color ToColour(this string colourInHtml)
        {
            int v = int.Parse(colourInHtml.Substring(1, colourInHtml.Length - 1), NumberStyles.HexNumber);
            Color c = Color.FromArgb(v);
            return Color.FromArgb(255, c);
        }

        /// <summary>
        /// Inverse a colour
        /// </summary>
        /// <param name="colourToInvert"></param>
        /// <returns></returns>
        public static Color InverseColour(this Color colourToInvert)
        {
            //pull each colour component and flip it (compiler demanded the cast)
            byte bRed = (byte) ~(colourToInvert.R);
            byte bGreen = (byte) ~(colourToInvert.G);
            byte bBlue = (byte) ~(colourToInvert.B);
            //set the text colour with overload of Argb
            return Color.FromArgb(bRed, bGreen, bBlue);
        }

        /// <summary>
        /// Tries to get the pastel colour equivalent to the dark colour given in parameter
        /// </summary>
        /// <param name="darkColor"></param>
        /// <returns></returns>
        public static Color ToPastel(this Color darkColor)
        {
            return SetBrightness(darkColor, 0.75);
        }

        /// <summary>
        /// Transform a colour into a RGB string ("128 255 44")
        /// </summary>
        /// <param name="colour"></param>
        /// <returns></returns>
        public static string ToRgbString(this Color colour)
        {
            return colour.R + " " + colour.G + " " + colour.B;
        }

        /// <summary>
        /// Sets the absolute brightness of a colour
        /// </summary>
        /// <param name="c">Original colour</param>
        /// <param name="brightness">The luminance level to impose</param>
        /// <returns>an adjusted colour</returns>
        public static Color SetBrightness(this Color c, double brightness)
        {
            Hsl hsl = HslToRgb(c);
            hsl.L = brightness;
            return HslToRgb(hsl);
        }


        /// <summary>
        /// Modifies an existing brightness level
        /// </summary>
        /// <remarks>
        /// To reduce brightness use a number smaller than 1. To increase brightness use a number larger than 1
        /// </remarks>
        /// <param name="c">The original colour</param>
        /// <param name="brightness">The luminance delta</param>
        /// <returns>An adjusted colour</returns>
        public static Color ModifyBrightness(this Color c, double brightness)
        {
            Hsl hsl = HslToRgb(c);
            hsl.L *= brightness;
            return HslToRgb(hsl);
        }


        /// <summary>
        /// Sets the absolute saturation level
        /// </summary>
        /// <remarks>Accepted values 0-1</remarks>
        /// <param name="c">An original colour</param>
        /// <param name="saturation">The saturation value to impose</param>
        /// <returns>An adjusted colour</returns>
        public static Color SetSaturation(this Color c, double saturation)
        {
            Hsl hsl = HslToRgb(c);
            hsl.S = saturation;
            return HslToRgb(hsl);
        }


        /// <summary>
        /// Modifies an existing Saturation level
        /// </summary>
        /// <remarks>
        /// To reduce Saturation use a number smaller than 1. To increase Saturation use a number larger than 1
        /// </remarks>
        /// <param name="c">The original colour</param>
        /// <param name="saturation">The saturation delta</param>
        /// <returns>An adjusted colour</returns>
        public static Color ModifySaturation(this Color c, double saturation)
        {
            Hsl hsl = HslToRgb(c);
            hsl.S *= saturation;
            return HslToRgb(hsl);
        }


        /// <summary>
        /// Sets the absolute Hue level
        /// </summary>
        /// <remarks>Accepted values 0-1</remarks>
        /// <param name="c">An original colour</param>
        /// <param name="hue">The Hue value to impose</param>
        /// <returns>An adjusted colour</returns>
        public static Color SetHue(this Color c, double hue)
        {
            Hsl hsl = HslToRgb(c);
            hsl.H = hue;
            return HslToRgb(hsl);
        }


        /// <summary>
        /// Modifies an existing Hue level
        /// </summary>
        /// <remarks>
        /// To reduce Hue use a number smaller than 1. To increase Hue use a number larger than 1
        /// </remarks>
        /// <param name="c">The original colour</param>
        /// <param name="hue">The Hue delta</param>
        /// <returns>An adjusted colour</returns>
        public static Color ModifyHue(this Color c, double hue)
        {
            Hsl hsl = HslToRgb(c);
            hsl.H *= hue;
            return HslToRgb(hsl);
        }

        /// <summary>
        /// Converts a colour from <c>Hsl</c> to RGB
        /// </summary>
        /// <remarks>Adapted from the algorithm in Foley and Van-Dam</remarks>
        /// <param name="hsl">The <c>Hsl</c> value</param>
        /// <returns>A <see cref="Color"/> structure containing the equivalent RGB values</returns>
        private static Color HslToRgb(Hsl hsl)
        {
            double r, g, b;

            if (hsl.L == 0)
            {
                r = g = b = 0;
            }
            else
            {
                if (hsl.S == 0)
                {
                    r = g = b = hsl.L;
                }
                else
                {
                    double temp2 = ((hsl.L <= 0.5) ? hsl.L*(1.0 + hsl.S) : hsl.L + hsl.S - (hsl.L*hsl.S));
                    double temp1 = 2.0*hsl.L - temp2;

                    double[] t3 = new[] {hsl.H + 1.0/3.0, hsl.H, hsl.H - 1.0/3.0};
                    double[] clr = new double[] {0, 0, 0};

                    for (int i = 0; i < 3; i++)
                    {
                        if (t3[i] < 0)
                            t3[i] += 1.0;

                        if (t3[i] > 1)
                            t3[i] -= 1.0;

                        if (6.0*t3[i] < 1.0)
                            clr[i] = temp1 + (temp2 - temp1)*t3[i]*6.0;
                        else if (2.0*t3[i] < 1.0)
                            clr[i] = temp2;
                        else if (3.0*t3[i] < 2.0)
                            clr[i] = (temp1 + (temp2 - temp1)*((2.0/3.0) - t3[i])*6.0);
                        else
                            clr[i] = temp1;
                    }

                    r = clr[0];
                    g = clr[1];
                    b = clr[2];
                }
            }

            return Color.FromArgb((int) (255*r), (int) (255*g), (int) (255*b));
        }

        /// <summary>
        /// Converts RGB to <c>Hsl</c>
        /// </summary>
        /// <remarks>Takes advantage of what's already built in to .NET by using the <c>Color.GetHue</c>, <c>Color.GetSaturation</c> and <c>Color.GetBrightness</c> methods</remarks>
        /// <param name="c">A <see cref="Color"/> to convert</param>
        /// <returns>An <c>Hsl</c> value</returns>
        private static Hsl HslToRgb(Color c)
        {
            var hsl = new Hsl();
            hsl.H = c.GetHue()/360.0; // we store hue as 0-1 as opposed to 0-360
            hsl.L = c.GetBrightness();
            hsl.S = c.GetSaturation();

            return hsl;
        }

        private class Hsl
        {
            private double h;
            private double l;
            private double s;

            public Hsl()
            {
                h = 0;
                s = 0;
                l = 0;
            }

            public double H
            {
                get { return h; }
                set
                {
                    h = value;
                    h = h > 1 ? 1 : h < 0 ? 0 : h;
                }
            }

            public double S
            {
                get { return s; }
                set
                {
                    s = value;
                    s = s > 1 ? 1 : s < 0 ? 0 : s;
                }
            }


            public double L
            {
                get { return l; }
                set
                {
                    l = value;
                    l = l > 1 ? 1 : l < 0 ? 0 : l;
                }
            }
        }
    }
}